March 97 - According to Script
ACCORDING TO SCRIPT
User Interactions in Apple Event-Driven
Applications
Cal Simone
So far throughout the history of the Macintosh, most applications have been designed to
be run by a user double-clicking the icon of an application (or document) in the
Finder and manipulating the application and its data through the graphical interface.
Recently, the publishing industry has adopted AppleScript and scriptable applications
as the mechanism for creating production systems. Now that scripting and Apple events
have become more pervasive, and more and more applications are scriptable,
applications must be prepared to be controlled remotely by Apple events from other
applications and scripts. And there's a new kind of application on the horizon, the
Apple event-based server application. A server application has no user interface: it's
designed to communicate with the outside world only through Apple events. Although
server applications have been possible to write since the introduction of System 7,
they're becoming increasingly important and will play a major role in the future
versions of the Mac OS.
In other words, there may not be a human being sitting at the computer where your
application is running.
In this column, I'll cover these topics:
• the two types of Apple-event control
• determining when your application should interact with a user
• how to interact, when a user is present
• how not to interact, when no user is present, and how to return errors
when you don't interact
There are some important differences between direct manipulation through the
graphical interface and remote control through interapplication communication. When
developing server applications, you'll want to be careful about what happens when an
Apple event (or series of Apple events) takes over. This applies not only to
applications that are designed to be controlled primarily through Apple events, but
also to those designed to be controlled mainly through a graphical interface but for
which Apple events provide an alternative interface to the graphical one. The
information presented in this column applies to both types of application.
TYPES OF APPLE-EVENT CONTROL
For this discussion, I'll classify Apple-event control into two scenarios:
• Simulating a user sitting at the computer -- Many users will write
scripts that help them automate their work. They'll generally still be at (or
near) the computer when the scripts run. In such cases, it's OK (or even
expected) that an application will interact with the user whenever there's a
problem or a choice needs to be made, or when the computer needs more input.
Many scripts that drive such applications will even incorporate interaction in
the form of dialog boxes. This scenario is often the case for embedded scripts,
such as those attached to tool palette icons.
• When a user isn't present -- This is the more interesting case for
automation and integration through interapplication communication, and will
become increasingly common as more intelligent scripts are written. Some
operations (and some applications) are more suited than others for unattended
batch processing. It's recommended for operations that take a long time, such
as those found in production systems; in these situations, direct interaction
with the user is usually not a good idea, since no one's likely to be there to deal
with a problem or a request for more information. Server applications deal
only with this scenario.
Since your application may need to respond to either type of Apple-event control, I'll
describe how to comfortably handle both, starting with the way to determine which one
you're dealing with at a particular moment. The largest issue to tackle in responding to
Apple events is whether to interact with the user. I'll explain how to determine
whether you should interact and what to do once you've made that determination.
Incidentally, in a standard factored application (you are factoring your application
these days, aren't you?) you can get most of the proper behavior for free.
An example scenario. Let's look at a common case that may or may not require
user interaction: handling the Core suite's Close event. According to the Apple Event Registry, one of the optional parameters for this event is the savingparameter, which
can have one of three enumerated values: yes, no, and ask. The traditional meanings of
these values are as follows:
• yes will always save a modified document, presenting a standard file
dialog if it has never been saved (unless the user included the saving
inparameter to specify where to save the document).
• no will never save a modified document, but rather discard any changes.
• ask will allow the user to choose whether to save the document, invoking
the user interface behavior.
But it's not quite that simple. For example, assume that the user modifies a document
in one of your application's windows and then runs a script that executes the command
close the front window(with no saving parameter or with saving ask). Your
application's Close event handler will typically display a dialog box asking the user
whether to save the document. The catch is that you can't always do this; it depends on
where the script is running. You could implement a preference setting that allows the
user to configure your application for "response to humans vs. non-humans," but
having too many preferences spoils the simplicity.
So what should you do? The solution is to call the routine AEInteractWithUser, and
then decide what to do based on its return result. But before getting into that, we'll
take a look at how the Apple Event Manager decides whether user interaction is
appropriate.
BEHIND THE SCENES
The following conditions are considered by the Apple Event Manager to determine
whether you should interact while handling an Apple event. Note that the first one is an
application setting, while the last two are optional attributes of a particular event that
may be set by the sender.
• the user interaction level -- whether your application allows
interactions in general
• the event source attribute -- where the particular Apple event came
from
• the event's interaction-requested attribute -- whether the Apple event
wants you to interact
The user interaction level. The Apple Event Manager first checks the user interaction level of your application. You can call AEGetInteractionAllowed yourself to
determine which Apple event sources can cause your application to interact with the
user. If you need to change the interaction level, you can set it at any time with
AESetInteractionAllowed.
The Apple Event Manager provides a data type, AEInteractAllowed, which is an
enumeration that defines three levels of allowable interaction:
• Interact with self -- At this level, you can interact with the user only in
response to Apple events you've sent to yourself, through your factored user
interface or from an attached or embedded script that you're executing inside
your application.
• Interact with local -- You can interact with the user in response to Apple
events originating from the same computer where your application is running.
This includes the above case.
• Interact with all -- You can interact with the user in response to any
Apple event, whether sent from the same computer or a remote machine.
Remote events will arrive only if all the conditions for accepting remote
events are met. You need to set two flags in the SIZE resource -- "accept
high-level events" (to receive any Apple events) and "allow local and remote
events" (to receive events from other computers) -- and give permission for
program linking in the Users and Groups control panel. You must also make
sure that Program Linking is turned on in the Sharing Setup control panel and
enabled for the application in the Finder's Sharing dialog.*
There's a fourth possibility for some applications, the true lowest level of interaction,
which is "no interaction." This is the appropriate level in a background-only
application or any pure server application, or any other situation where interaction is
undesirable. Consider this example (which Jon Pugh came across when he was working
for Storm Technologies, while implementing scriptability in PhotoFlash): An attached
script is executed as part of an automation process. The script gets an error and the
application puts up a dialog box -- but there's no one there to answer it. If users can
execute a script inside your application and the script might be run without a user
present, you'll have this problem. Before running the script, the user needs to be able
to tell your application, "No one will be here, so don't interact.
Since the Apple Event Manager doesn't provide support for the no-interaction
condition (the AEInteractAllowed type has only three possible values), you'll have to
set up and maintain this yourself. The simplest way to implement this setting is by
using a global Boolean variable for the no-interaction flag. If the variable's value is
true and your application is called on to do interaction, you'll know right away not to
allow the interaction. (Note that if your application handles multiple concurrent Apple
events using threading, an application global is not a good solution.)
Because AppleScript doesn't provide a way to get or set interaction levels from
scripts, you'll need to implement a user interaction level property for your
application, similar to the one in PhotoFlash, that a user can set or get from a script.
This property should handle all four enumeration constants and associate the fourth
level, no interaction, with the global Boolean variable. In your 'aete' resource, use the
following terminology and 4-byte codes for the enumeration constants:
• never interact -- 'eNvr'
• interact with self -- 'eInS'
• interact with local -- 'eInL'
• interact with all -- 'eInA'
Apple events initiated from the user interface should probably ignore the
no-interaction flag altogether, and just call AEInteractWithUser and interact in the
usual way if need be. This way your application could perform double duty,
successfully performing actions initiated by Apple events from other sources without
disturbing a user who might be sitting there using the application.
The event source attribute. The next step taken by the Apple Event Manager is to
examine the event source attribute of the particular Apple event being handled. If you
want to look at the source for an Apple event, you can call AEGetParamPtr with the
keyEventSourceAttr keyword to obtain the source. The source data type,
AEEventSource, is an enumeration indicating five possible sources: unknown, direct
call, same process, local process, and remote process. This is checked against the user
interaction level to find out whether your application allows user interaction in
response to an Apple event from this particular source.
The interaction-requested attribute. Finally, if the Apple Event Manager
determines that interaction with the user in response to the event source is OK, it
examines the event's interaction-requested attribute, which tells the Apple Event
Manager what kinds of interaction are requested by the Apple event. If you want to look
at this level yourself, you can call AEGetParamPtr with the keyInteractLevelAttr
keyword to obtain the interaction level requested by the Apple event. There are three
constants that represent the interaction levels:
• kAEAlwaysInteract -- Your application can interact with the user for any
reason, such as to confirm an action, notify the user of something, or request
information.
• kAECanInteract -- Your application can interact with the user if it needs
to request information from the user to continue.
• kAENeverInteract -- Your application should never present a user
interface while handling this Apple event.
When present, an additional constant that's set by the sender of the event,
kAECanSwitchLayer, contributes to determining whether you may bring yourself to
the foreground if you need to interact.
If the interaction-requested attribute is present, both its value and the user